using System;
using System.Collections.Generic;
using Lucene.Net.Search;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using System.Text;
using Lucene.Net.Documents;
using log4net;
using Lucene.Net.Analysis;

namespace ODSearchEngine.Lucene
{
	public class LuceneSearchEngine : SearchEngineBase
	{
		private static readonly ILog logger = LogManager.GetLogger(typeof(LuceneSearchEngine));
		IndexSearcher searcher = null;
		IndexReader reader = null;
		QueryParser parser = new QueryParser(Constants.version, Constants.defaultFieldName, Constants.defaultAnalyzer);
		
		public LuceneSearchEngine () : base()
		{
			
		}
		
		#region implemented abstract members of ODSearchEngine.SearchEngineBase
		
		public override void Init()
		{
			logger.Info("Initializing search engine.");
			this.reader = IndexReader.Open(Constants.indexDirectory, true);
			this.searcher = new IndexSearcher(reader);
		}
		
		public override IEnumerable<SearchHit> Search (string key, string query)
		{
			logger.DebugFormat("Searching for '{0}' in '{1}'", query, key);
			return this.Search(this.BuildQuery(key.ToLower(), query.ToLower()), key);
		}
		
		
		public override IEnumerable<SearchHit> Search (string[] keys, string query)
		{
			return this.Search(this.BuildQuery(keys, query), null);
		}
		
		
		public override IEnumerable<SearchHit> Search (IDictionary<string, string> queries)
		{
			throw new System.NotImplementedException();
		}
		
		
		public override IDictionary<string, string> GetData (SearchHit hit)
		{
			IDictionary<string, string> result = null;
			if(logger.IsDebugEnabled)
				logger.DebugFormat("Getting data for {0}.", hit);
			Query q = this.BuildQueryForSearchByID(hit.ID.ToString());
			var docs = this.searcher.Search(q, 42);
			if(docs.scoreDocs.Length > 0)
			{
				result = new Dictionary<string, string>();
				result.Add(Constants.fieldID, hit.ID.ToString());
				foreach(var doc in docs.scoreDocs)
				{
					var tmp = this.reader.Document(doc.doc);
					var pNameField = tmp.GetField(Constants.fieldParameterName);
					var pValueField = tmp.GetField(Constants.fieldParameterValue);
					var pName = pNameField.StringValue();
					var pValue = pValueField.StringValue();
					if(result.ContainsKey(pName)) // TODO: remove this check, when unique ID is guaranteed
						result.Remove(pName);
					result.Add(pName, pValue);
				}
			}
			if(logger.IsDebugEnabled)
				logger.DebugFormat("Built data. {0}", result);
			return result;
		}
		
		
		public override IDictionary<string, string> GetData (IEnumerable<SearchHit> hits)
		{			
			throw new System.NotImplementedException();
		}		
		#endregion
		
		private Query BuildQuery(string pName, string queryString)
		{
			queryString = LuceneSearchEngine.EscapeSpecialCharacters(queryString);
			BooleanQuery result = new BooleanQuery();
			result.Add(new TermQuery(
			                         new Term(Constants.fieldParameterName, pName)
			                        ),			           				
			           BooleanClause.Occur.MUST);
			result.Add(new WildcardQuery(
			                         new Term(Constants.fieldParameterValue, queryString)
			                         ),
			           BooleanClause.Occur.MUST);			
		
			if(logger.IsDebugEnabled)				
				logger.DebugFormat("Building query. Result: {0}", result);
			return result;
			
		}
		
		private Query BuildQuery(string [] pNames, string queryString) // TODO: Build queries as objects
		{			
			Query result = null;
			queryString = LuceneSearchEngine.EscapeSpecialCharacters(queryString);
			StringBuilder finalQueryString = new StringBuilder(42);			
			for(int i = 0; i < pNames.Length; i++)
			{				
				finalQueryString.Append("(");
				finalQueryString.Append(Constants.fieldParameterName);
				finalQueryString.Append(":");
				finalQueryString.Append(pNames[i]);
				finalQueryString.Append(" AND ");
				finalQueryString.Append(Constants.fieldParameterValue);
				finalQueryString.Append(":");
				finalQueryString.Append(queryString);
				finalQueryString.Append(")");
				if(i != pNames.Length - 1)
					finalQueryString.Append(" OR ");
			}
			logger.DebugFormat("Building query. Result: {0}", finalQueryString);
			result = parser.Parse(finalQueryString.ToString());
			return result;
		}
		
		private Query BuildQueryForSearchByID(string id)
		{			
			Query result = new TermQuery(new Term(Constants.fieldID, id));
			if(logger.IsDebugEnabled)
				logger.DebugFormat("Building query for search by ID \'{0}\', result: {1}", id, result);
			return result;
		}
		
		private IEnumerable<SearchHit> Search(Query query, string key)
		{
			LinkedList<SearchHit> result = new LinkedList<SearchHit>();
			this.reader = IndexReader.Open(Constants.indexDirectory, true);
			this.searcher = new IndexSearcher(reader);
			TopDocs td = searcher.Search(query, 100);
			logger.DebugFormat("Searching. {0}", query);
			foreach(var d in td.scoreDocs)
			{
				string id = this.reader.Document(d.doc).GetField(Constants.fieldID).StringValue();				
				result.AddLast(new SearchHit(id, d.score, key, null));
			}
			return result;
		}
		
		private static string EscapeSpecialCharacters(string original)
		{
			return original;
		}
		
		private static IDictionary<string, string> DocToDictionary(Document document)
		{
			var result = new Dictionary<string, string>();
			foreach(var obj in document.GetFields())
			{
				Field field = obj as Field;
				if(field != null)
				{
					result.Add(field.Name(), field.StringValue());
				}
			}
			return result;
		}
		
	}
}

